package nature.nano.control.script.assembly;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import nature.nano.component.SimpleProps;
import nature.nano.component.Strings;
import nature.nano.environment.StaticErrors;
import nature.nano.iface.Accepter;
import nature.nano.iface.Props;
import nature.nano.iface.secondary.SlotBoard;
import nature.nano.iface.secondary.Startable;
import nature.nano.iface.secondary.Stoppable;
import nature.nano.util.pipe.ErrorNormalizer;
import nature.nano.util.pipe.EventMixer;

class ScriptParser {

	private Accepter errorAccepter;
	
	private Controller controller = new Controller();
	
	private Map<String, ClassStaticInfo> clazzAliasSet = new HashMap<String, ClassStaticInfo>();	
	private Map<String, SlotAliasInfo> slotAliasSet = new HashMap<String, SlotAliasInfo>();
	private ClassParsingInfo last;
	
	private Set<SlotBoard> boards = new HashSet<SlotBoard>();
	private EventMixer errors = new EventMixer();
	
	public ScriptParser(Accepter errorAccepter) {
		this.errorAccepter = errorAccepter;
		addBuiltinClazzs();
	}
	
	private void addBuiltinClazzs() {
		addBuiltinClazz(Controller.class.getSimpleName(), controller);
		addBuiltinClazz("Errors", errors);
		addBuiltinClazz("StaticErrors", StaticErrors.instance());
	}
	
	private void addBuiltinClazz(String name, Object object) {
		clazzAliasSet.put(name, new ClassStaticInfo(object));
		
		if (object instanceof Startable) {
			controller.addStarters((Startable) object);
		}
		if (object instanceof Stoppable) {
			controller.addStoppers((Stoppable) object);
		}
	}
	
	public void newRow() {
		last = null;
	}

	public boolean addClass(Props clazzProp, Props propsProp) {
		String name = clazzProp.get(Strings.clazz);
		String slot = clazzProp.get(Strings.slot);
		String slotAlias = clazzProp.get(Strings.slotAlias);
		String clazzAlias = clazzProp.get(Strings.clazzAlias);
		
		ClassParsingInfo current = new ClassParsingInfo(clazzAliasSet.get(name), name, slot, slotAlias, clazzAlias);
		if (!current.valid()) {
			SlotAliasInfo info = slotAliasSet.get(name);
			if (info != null) {
				if (slot == null) {
					slot = info.getSlot();
				}
				current = new ClassParsingInfo(info.getOrigin(), name, slot, slotAlias, clazzAlias);
			}
		}
		
		if (!current.valid()) {
			current = new ClassParsingInfo(clazzProp, propsProp);
			if (!current.valid()) {
				writeError(Strings.errorType, "can't-create-class-instance", Strings.clazz, name);
				return false;
			}			
		}

		if (clazzAlias != null) {
			ClassStaticInfo old = clazzAliasSet.get(clazzAlias);
			if (old != null && old != current.getOrigin()) {
				writeError(Strings.errorType, "object-name-already-exists", Strings.name, clazzAlias);
			} else {
				clazzAliasSet.put(clazzAlias, current.getOrigin());
			}
		}
		if (slotAlias != null) {
			SlotAliasInfo old = slotAliasSet.get(slotAlias);
			if (old != null && !(old.getSlot().equals(current.getSlot()) && current.getOrigin() == old.getOrigin())) {
				writeError(Strings.errorType, "stream-name-already-exists", Strings.name, slotAlias);					
			} else {
				slotAliasSet.put(slotAlias, new SlotAliasInfo(current.getOrigin(), current.getSlot()));
			}
		}
		
		putInBoards(current);
		putInController(current);
		connectLast(current);
		
		last = current;
		
		return true;
	}

	private void putInBoards(ClassParsingInfo current) {
		Object object = current.getOrigin().getObject();
		if (object instanceof SlotBoard) {
			boards.add((SlotBoard) object);
		}		
	}

	private void putInController(ClassParsingInfo current) {
		if (!current.isAliasObject()) {
			Object object = current.getOrigin().getObject();
			if (object instanceof Startable) {
				controller.addStarters((Startable) object);
			}
			if (object instanceof Stoppable) {
				controller.addStoppers((Stoppable) object);
			}
		}
	}

	private void connectLast(ClassParsingInfo current) {
		if (last == null) {
			return;
		}
		boolean succeeded = last.plug(current.getOrigin());
		if (!succeeded) {
			writeError(
				Strings.errorType, "objects-connecting-failed",
				"first", last.getName(),
				"first-slot", last.getSlot(),
				"next", current.getName());
		}
	}

	public void start() {
		catchErrors();
		controller.start();
	}

	private void catchErrors() {
		for (SlotBoard board : boards) {
			Accepter accepter = board.getAccepter(Strings.error);
			if (accepter == null) {
				ErrorNormalizer formater = new ErrorNormalizer();
				board.plug(Strings.error, formater);
				formater.plug(Strings.output, errors);
			}
		}
	}
	
	private void writeError(String... props) {
		errorAccepter.onBegin(Strings.error, new SimpleProps(props));
		errorAccepter.onEnd(Strings.error);
	}
	
}
